package com.ikingtech.platform.service.system.menu.controller;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ikingtech.framework.sdk.base.model.DragOrderParam;
import com.ikingtech.framework.sdk.context.event.SystemInitEvent;
import com.ikingtech.framework.sdk.context.event.TenantDeleteEvent;
import com.ikingtech.framework.sdk.context.event.TenantInitEvent;
import com.ikingtech.framework.sdk.context.event.application.ApplicationMenuRemoveEvent;
import com.ikingtech.framework.sdk.context.event.application.DevApplicationMenuInitEvent;
import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.context.security.Me;
import com.ikingtech.framework.sdk.core.response.R;
import com.ikingtech.framework.sdk.data.mybatisplus.helper.DragHelper;
import com.ikingtech.framework.sdk.data.mybatisplus.helper.DragHelperBuilder;
import com.ikingtech.framework.sdk.enums.common.DragTargetPositionEnum;
import com.ikingtech.framework.sdk.enums.domain.DomainEnum;
import com.ikingtech.framework.sdk.enums.system.menu.*;
import com.ikingtech.framework.sdk.log.embedded.annotation.OperationLog;
import com.ikingtech.framework.sdk.menu.api.MenuApi;
import com.ikingtech.framework.sdk.menu.api.MenuRoleApi;
import com.ikingtech.framework.sdk.menu.api.MenuUserApi;
import com.ikingtech.framework.sdk.menu.model.MenuDTO;
import com.ikingtech.framework.sdk.menu.model.RoleMenuAssignDTO;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.framework.sdk.web.annotation.ApiController;
import com.ikingtech.platform.service.system.menu.entity.MenuDO;
import com.ikingtech.platform.service.system.menu.exception.MenuExceptionInfo;
import com.ikingtech.platform.service.system.menu.service.repository.MenuRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * 系统管理-菜单模块
 * <p>1. 菜单数据需按租户隔离
 *
 * @author tie yan
 */
@Slf4j
@RequiredArgsConstructor
@ApiController(value = "/system/menu", name = "系统管理-菜单模块", description = "系统管理-菜单模块")
public class MenuController implements MenuApi {

    private final MenuRepository repo;

    private final MenuRoleApi menuRoleApi;

    private final MenuUserApi menuUserApi;

    /**
     * 添加菜单
     *
     * @param menu 菜单信息
     * @return 返回添加结果
     */
    @Override
    @OperationLog(value = "新增菜单", dataId = "#_res.getData()")
    @Transactional(rollbackFor = Exception.class)
    public R<String> add(MenuDTO menu) {
        if (this.repo.exists(Wrappers.<MenuDO>lambdaQuery().eq(MenuDO::getParentId, menu.getParentId()).eq(MenuDO::getName, menu.getName()))) {
            throw new FrameworkException(MenuExceptionInfo.DUPLICATE_MENU_NAME);
        }
        // 将MenuDTO对象转换为MenuDO对象
        MenuDO entity = Tools.Bean.copy(menu, MenuDO.class);
        // 生成菜单的唯一ID
        entity.setId(Tools.Id.uuid());
        // 生成菜单的完整路径
        entity.setFullPath(this.repo.parseFullPath(menu.getParentId(), entity.getId()));
        // 生成菜单的权限代码，如果为空则生成唯一ID
        entity.setPermissionCode(Tools.Str.isBlank(menu.getPermissionCode()) ? Tools.Id.uuid() : menu.getPermissionCode());
        // 获取菜单的排序顺序，如果为空则获取最大排序顺序并加1
        entity.setSortOrder(this.getMaxSortOrder(menu.getParentId(), menu.getAppCode()) + 1);
        // 设置菜单的领域代码、应用代码和租户代码
        entity.setDomainCode(Me.domainCode());
        entity.setAppCode(Me.appCode());
        entity.setTenantCode(Me.tenantCode());
        // 保存菜单到数据库
        this.repo.save(entity);
        // 返回添加结果
        return R.ok(entity.getId());
    }

    /**
     * 删除菜单
     *
     * @param id 菜单ID
     * @return 删除结果
     */
    @Override
    @OperationLog(value = "删除菜单")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> delete(String id) {
        // 根据ID查询菜单实体
        MenuDO entity = this.repo.getById(id);
        if (null == entity) {
            return R.ok();
        }
        // 查询所有同级菜单ID
        List<String> menuIds = this.repo.listObjs(Wrappers.<MenuDO>lambdaQuery().select(MenuDO::getId).likeRight(MenuDO::getFullPath, entity.getFullPath()));
        // 删除菜单实体及其同级菜单
        this.repo.removeByIds(menuIds);
        // 删除菜单实体及其同级菜单对应的权限
        this.menuRoleApi.removeRoleMenu(menuIds);
        return R.ok();
    }

    /**
     * 更新菜单
     *
     * @param menu 菜单对象
     * @return 返回更新结果
     */
    @Override
    @OperationLog(value = "更新菜单")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> update(MenuDTO menu) {
        if (this.repo.exists(Wrappers.<MenuDO>lambdaQuery()
                .ne(MenuDO::getId, menu.getId())
                .eq(MenuDO::getParentId, menu.getParentId())
                .and(Tools.Str.isBlank(Me.tenantCode()), wrapper -> wrapper.eq(MenuDO::getTenantCode, Tools.Str.EMPTY).or().isNull(MenuDO::getTenantCode))
                .eq(Tools.Str.isNotBlank(Me.tenantCode()), MenuDO::getTenantCode, Me.tenantCode())
                .eq(MenuDO::getName, menu.getName()))) {
            throw new FrameworkException(MenuExceptionInfo.DUPLICATE_MENU_NAME);
        }
        // 将传入的MenuDTO对象转换为MenuDO对象
        MenuDO newEntity = Tools.Bean.copy(menu, MenuDO.class);

        // 判断菜单的可见性和菜单视图类型是否发生了变化
        if (!newEntity.getVisible().equals(menu.getVisible()) ||
            !Tools.Str.equals(newEntity.getMenuViewType(), menu.getMenuViewType().name())) {
            // 获取与新菜单全路径相同的菜单列表
            List<MenuDO> entities = this.repo.list(Wrappers.<MenuDO>lambdaQuery().likeRight(MenuDO::getFullPath, newEntity.getFullPath()));
            // 更新符合条件的菜单列表的可见性和菜单视图类型
            this.repo.updateBatchById(Tools.Coll.traverse(entities, entity -> {
                entity.setVisible(menu.getVisible());
                entity.setMenuViewType(menu.getMenuViewType().name());
                return entity;
            }));
        }
        // 更新新菜单对象
        this.repo.updateById(newEntity);
        // 返回更新结果
        return R.ok();
    }

    /**
     * 获取菜单详情
     *
     * @param id 菜单ID
     * @return 菜单对象
     */
    @Override
    public R<MenuDTO> detail(String id) {
        // 通过ID获取菜单实体对象
        MenuDO entity = this.repo.getById(id);
        // 如果实体对象为空，则抛出菜单不存在异常
        if (null == entity) {
            throw new FrameworkException(MenuExceptionInfo.MENU_NOT_FOUND);
        }
        // 将实体对象转换为菜单DTO对象并返回
        return R.ok(this.modelConvert(entity));
    }

    @Override
    public R<MenuDTO> getByCode(String permissionCode) {
        // 通过ID获取菜单实体对象
        MenuDO entity = this.repo.getOne(Wrappers.<MenuDO>lambdaQuery()
                .eq(MenuDO::getPermissionCode, permissionCode)
                .eq(Tools.Str.isNotBlank(Me.domainCode()), MenuDO::getDomainCode, Me.domainCode())
                .eq(Tools.Str.isNotBlank(Me.tenantCode()), MenuDO::getTenantCode, Me.tenantCode())
                .eq(Tools.Str.isNotBlank(Me.appCode()), MenuDO::getAppCode, Me.appCode()));
        // 如果实体对象为空，则抛出菜单不存在异常
        if (null == entity) {
            throw new FrameworkException(MenuExceptionInfo.MENU_NOT_FOUND);
        }
        // 将实体对象转换为菜单DTO对象并返回
        return R.ok(this.modelConvert(entity));
    }

    /**
     * 获取所有菜单信息
     *
     * @return 菜单信息列表
     */
    @Override
    public R<List<MenuDTO>> all() {
        return R.ok(this.modelConvert(this.repo.list(MenuRepository.createWrapper(Me.domainCode(), Me.tenantCode(), Me.appCode(), Tools.Str.EMPTY, true))));
    }

    /**
     * 根据角色ID获取角色菜单分配列表
     *
     * @param roleId 角色ID
     * @return 角色菜单分配DTO对象
     */
    @Override
    public R<RoleMenuAssignDTO> listByRoleId(String roleId) {
        RoleMenuAssignDTO result = new RoleMenuAssignDTO();
        result.setRoleId(roleId);
        // 加载角色对应的菜单ID列表
        List<String> menuIds = this.menuRoleApi.loadMenuId(roleId);
        // 存储角色已分配的菜单权限码列表
        List<String> roleMenuPermissionCodes = new ArrayList<>();
        // 存储角色已分配的菜单数量统计
        Map<String, Integer> roleAssignedMenuCountMap = new HashMap<>();
        if (Tools.Coll.isNotBlank(menuIds)) {
            // 根据菜单ID列表查询角色已分配的菜单实体列表
            List<MenuDO> roleAssignedMenuEntities = this.repo.listByIds(menuIds);
            // 将角色已分配的菜单权限码添加到列表中
            roleMenuPermissionCodes.addAll(Tools.Coll.convertList(roleAssignedMenuEntities, MenuDO::getPermissionCode));
            // 统计角色已分配的菜单数量
            roleAssignedMenuCountMap.putAll(this.calculateMenuCount(roleAssignedMenuEntities));
        }
        // 查询所有菜单实体列表
        List<MenuDO> entities = this.repo.list(MenuRepository.createWrapper(Me.domainCode(), Me.tenantCode(), Me.appCode(), Tools.Str.EMPTY, true));
        // 统计所有菜单数量
        Map<String, Integer> allMenuCountMap = this.calculateMenuCount(entities);
        // 将所有菜单转换为菜单DTO对象，并根据角色已分配的菜单权限码进行标记
        result.setMenus(Tools.Coll.convertList(entities, entity -> {
            MenuDTO menu = this.modelConvert(entity);
            if (roleMenuPermissionCodes.contains(entity.getPermissionCode())) {
                if (Objects.equals(roleAssignedMenuCountMap.get(entity.getPermissionCode()), allMenuCountMap.get(entity.getPermissionCode()))) {
                    menu.setRoleAssigned(RoleMenuAssignedStatusEnum.CHECKED);
                } else {
                    menu.setRoleAssigned(RoleMenuAssignedStatusEnum.PARTIAL_CHECKED);
                }
            } else {
                menu.setRoleAssigned(RoleMenuAssignedStatusEnum.UNCHECKED);
            }
            return menu;
        }));
        return R.ok(result);
    }

    /**
     * 根据登录用户获取菜单列表
     *
     * @return 菜单列表
     */
    @Override
    public R<List<MenuDTO>> listByLoginUser(MenuViewTypeEnum viewType, Boolean showAllViewType) {
        String viewTypeName = null == viewType ? MenuViewTypeEnum.MANAGE.name() : viewType.name();
        if (Me.isAdmin()) {
            // 如果是管理员，则返回所有菜单列表
            return R.ok(this.modelConvert(this.repo.list(MenuRepository.createWrapper(Me.domainCode(), Me.tenantCode(), Me.appCode(), viewTypeName, showAllViewType))));
        } else {
            // 如果不是管理员，则根据用户角色获取菜单列表
            List<String> roleIds = this.menuUserApi.loadRoleId(Me.id(), Me.domainCode(), Me.tenantCode(), Me.appCode());
            if (Tools.Coll.isBlank(roleIds)) {
                // 如果用户没有角色，则返回空列表
                return R.ok(new ArrayList<>());
            }
            List<String> menuIds = this.menuRoleApi.loadMenuId(roleIds);
            if (Tools.Coll.isBlank(menuIds)) {
                // 如果用户角色没有对应的菜单，则返回空列表
                return R.ok(new ArrayList<>());
            }
            // 根据菜单ID获取菜单列表
            return R.ok(this.modelConvert(this.repo.list(Wrappers.<MenuDO>lambdaQuery().in(MenuDO::getId, menuIds).eq(MenuDO::getMenuViewType, viewTypeName))));
        }
    }

    @Override
    public R<Object> drag(DragOrderParam dragParam) {
        DragHelper<MenuDO> dragHelper = DragHelperBuilder.builder(
                        () -> this.repo.list(Wrappers.<MenuDO>lambdaQuery().eq(MenuDO::getParentId, dragParam.getParentId()).gt(MenuDO::getSortOrder, dragParam.getCurrentOrder())),
                        () -> this.repo.list(Wrappers.<MenuDO>lambdaQuery().eq(MenuDO::getParentId, dragParam.getTargetParentId()).gt(MenuDO::getSortOrder, dragParam.getTargetOrder())),
                        (startOrder, endOrder) -> this.repo.list(Wrappers.<MenuDO>lambdaQuery().eq(MenuDO::getParentId, dragParam.getParentId()).between(MenuDO::getSortOrder, startOrder, endOrder))
                )
                .which(dragParam.getParentId(), dragParam.getTargetParentId(), DragTargetPositionEnum.INNER.equals(dragParam.getPosition()))
                .currentNode(() -> this.repo.getById(dragParam.getCurrentId()))
                .targetNode(() -> this.repo.getById(dragParam.getTargetId()))
                .targetParentNodeFullPath(() -> this.repo.getObj(Wrappers.<MenuDO>lambdaQuery().select(MenuDO::getFullPath).eq(MenuDO::getId, dragParam.getTargetParentId()), String.class::cast))
                .maxSortOrder(() -> this.getMaxSortOrder(dragParam.getTargetId(), this.repo.getObj(Wrappers.<MenuDO>lambdaQuery().select(MenuDO::getAppCode).eq(MenuDO::getId, dragParam.getCurrentId()), String.class::cast)))
                .beforeTarget(DragTargetPositionEnum.BEFORE.equals(dragParam.getPosition()))
                .build();
        this.repo.updateBatchById(dragHelper.drag());
        return R.ok();
    }

    /**
     * 注册系统初始化事件监听器
     */
    @EventListener()
    @Transactional(rollbackFor = Exception.class)
    public void systemInitEventListener(SystemInitEvent initEvent) {
        List<MenuDO> allExistEntities = this.repo.list(Wrappers.<MenuDO>lambdaQuery()
                .eq(MenuDO::getPreset, true)
                .eq(MenuDO::getDomainCode, DomainEnum.PLATFORM.name()));
        List<MenuDO> entities = new ArrayList<>();
        Map<String, MenuDO> allExistEntityMap = Tools.Coll.convertMap(allExistEntities, MenuDO::getPermissionCode);
        Tools.Array.filter(PlatformMenuEnum.values(), platformMenu -> Tools.Coll.isNotBlank(platformMenu.initDomains()) && platformMenu.initDomains().contains(DomainEnum.PLATFORM)).forEach(platformMenu -> {
            if (!allExistEntityMap.containsKey(platformMenu.permissionCode())) {
                entities.add(this.createMenu(platformMenu, DomainEnum.PLATFORM.name(), Tools.Str.EMPTY, platformMenu.appCode()));
            }
            allExistEntityMap.remove(platformMenu.permissionCode());
        });
        if (Tools.Coll.isNotBlank(entities)) {
            this.retrieveMenuParentIdAndFullPath(entities, Tools.Coll.convertMap(allExistEntities, MenuDO::getPermissionCode));
            this.repo.saveBatch(entities);
        }
        if (Tools.Coll.isNotBlankMap(allExistEntityMap)) {
            this.repo.remove(Wrappers.<MenuDO>lambdaQuery()
                    .in(MenuDO::getPermissionCode, allExistEntityMap.keySet()));
        }
        if (Tools.Coll.isNotBlank(initEvent.getTenantCodes())) {
            List<MenuDO> tenantEntities = new ArrayList<>();
            List<String> menuIdForDelete = new ArrayList<>();
            initEvent.getTenantCodes().forEach(tenantCode -> {
                List<MenuDO> allExistTenantEntities = this.repo.list(Wrappers.<MenuDO>lambdaQuery()
                        .eq(MenuDO::getDomainCode, DomainEnum.TENANT.name())
                        .eq(MenuDO::getPreset, true)
                        .eq(MenuDO::getTenantCode, tenantCode));
                Map<String, MenuDO> allExistTenantEntityMap = Tools.Coll.convertMap(allExistTenantEntities, MenuDO::getPermissionCode);
                List<MenuDO> currentTenantEntities = new ArrayList<>();
                Tools.Array.filter(PlatformMenuEnum.values(), platformMenu -> Tools.Coll.isNotBlank(platformMenu.initDomains()) && platformMenu.initDomains().contains(DomainEnum.TENANT)).forEach(platformMenu -> {
                    if (!allExistTenantEntityMap.containsKey(platformMenu.permissionCode())) {
                        currentTenantEntities.add(this.createMenu(platformMenu, DomainEnum.TENANT.name(), tenantCode, platformMenu.appCode()));
                    }
                    allExistTenantEntityMap.remove(platformMenu.permissionCode());
                });
                if (Tools.Coll.isNotBlank(currentTenantEntities)) {
                    this.retrieveMenuParentIdAndFullPath(currentTenantEntities, Tools.Coll.convertMap(allExistTenantEntities, MenuDO::getPermissionCode));
                    tenantEntities.addAll(currentTenantEntities);
                }
                if (Tools.Coll.isNotBlankMap(allExistTenantEntityMap)) {
                    menuIdForDelete.addAll(Tools.Coll.convertList(allExistTenantEntityMap.values(), MenuDO::getId));
                }
            });
            if (Tools.Coll.isNotBlank(menuIdForDelete)) {
                this.repo.removeBatchByIds(menuIdForDelete);
            }
            if (Tools.Coll.isNotBlank(tenantEntities)) {
                this.repo.saveBatch(tenantEntities);
            }
        }
    }

    /**
     * 处理租户初始化事件
     */
    @EventListener
    @Transactional(rollbackFor = Exception.class)
    public void tenantInitEventListener(TenantInitEvent event) {
        List<MenuDO> existEntities = this.repo.list(Wrappers.<MenuDO>lambdaQuery()
                .eq(MenuDO::getDomainCode, DomainEnum.TENANT.name())
                .eq(MenuDO::getTenantCode, Me.tenantCode()));
        Map<String, MenuDO> existMenuMap = Tools.Coll.convertMap(existEntities, MenuDO::getPermissionCode);
        List<MenuDO> entities = Tools.Coll.flatMap(Tools.Array.convertList(PlatformMenuEnum.values(), value -> Tools.Coll.convertList(
                value.initDomains(),
                domain -> DomainEnum.TENANT.name().equals(domain.name()),
                domain -> this.createMenu(value, domain.name(), event.getCode(), Tools.Str.EMPTY))));
        this.retrieveMenuParentIdAndFullPath(entities, existMenuMap);
        if (Tools.Coll.isNotBlank(entities)) {
            this.repo.saveBatch(Tools.Coll.filter(entities, entity -> !existMenuMap.containsKey(entity.getPermissionCode())));
        }
    }

    /**
     * 处理租户删除事件
     */
    @EventListener
    @Transactional(rollbackFor = Exception.class)
    public void tenantDeleteEventListener(TenantDeleteEvent event) {
        // 获取租户下的菜单列表
        List<String> menuIds = this.repo.listObjs(Wrappers.<MenuDO>lambdaQuery()
                .eq(MenuDO::getTenantCode, event.getCode()), String.class::cast);
        if (Tools.Coll.isNotBlank(menuIds)) {
            // 删除菜单
            this.repo.removeBatchByIds(menuIds);
            // 删除角色菜单关联关系
            this.menuRoleApi.removeRoleMenu(menuIds);
        }
    }


    /**
     * 处理DevApplicationMenuInitEvent事件的方法
     */
    @EventListener
    @Transactional(rollbackFor = Exception.class)
    public void devAppMenuInitEventListener(DevApplicationMenuInitEvent event) {
        // 删除非租户应用的菜单
        this.repo.remove(Wrappers.<MenuDO>lambdaQuery().eq(MenuDO::getDomainCode, DomainEnum.TENANT.name()).ne(MenuDO::getTenantCode, "APP_PREVIEW").eq(MenuDO::getAppCode, event.getAppCode()));

        // 获取应用预览的菜单实体列表
        List<MenuDO> previewMenuEntities = this.repo.list(Wrappers.<MenuDO>lambdaQuery()
                .eq(MenuDO::getDomainCode, DomainEnum.APPLICATION.name())
                .eq(MenuDO::getTenantCode, "APP_PREVIEW")
                .eq(MenuDO::getAppCode, event.getAppCode())
                .orderByAsc(MenuDO::getSortOrder));

        // 将预览菜单实体列表转换为键值对形式的Map
        Map<String, String> previewMenuMap = Tools.Coll.convertMap(previewMenuEntities, MenuDO::getId, MenuDO::getPermissionCode);

        // 保存或更新菜单实体列表
        this.repo.saveOrUpdateBatch(Tools.Coll.flatMap(event.getTenantCodes(), tenantCode -> {
            // 遍历预览菜单实体列表
            List<MenuDO> tenantMenuEntities = Tools.Coll.traverse(
                    previewMenuEntities,
                    entity -> {
                        // 为每个菜单实体生成新的ID，并设置租户代码、领域代码
                        entity.setId(Tools.Id.uuid());
                        entity.setTenantCode(tenantCode);
                        entity.setDomainCode(DomainEnum.TENANT.name());
                        return entity;
                    }
            );

            // 将租户菜单实体列表转换为键值对形式的Map
            Map<String, String> newMenuMap = Tools.Coll.convertMap(tenantMenuEntities, MenuDO::getPermissionCode, MenuDO::getId);

            // 遍历租户菜单实体列表
            return Tools.Coll.traverse(tenantMenuEntities, entity -> {
                // 设置菜单实体的父菜单ID为预览菜单实体列表中的对应菜单ID
                entity.setParentId(newMenuMap.get(previewMenuMap.get(entity.getParentId())));

                // 设置菜单实体的完整路径为预览菜单实体列表中的对应菜单路径
                entity.setFullPath(Tools.Coll.join(
                        Tools.Coll.traverse(
                                Tools.Str.split(entity.getFullPath(), "@"),
                                previewFullPath -> newMenuMap.get(previewMenuMap.get(previewFullPath))
                        ),
                        "@"
                ));

                return entity;
            });
        }, Collection::stream));
    }

    /**
     * 应用菜单删除事件监听器
     */
    @EventListener
    @Transactional(rollbackFor = Exception.class)
    public void applicationMenuRemoveEventListener(ApplicationMenuRemoveEvent event) {
        // 根据应用代码删除菜单
        this.repo.remove(Wrappers.<MenuDO>lambdaQuery().eq(MenuDO::getAppCode, event.getAppCode()));
    }

    private List<MenuDTO> modelConvert(List<MenuDO> entities) {
        return Tools.Coll.convertList(entities, this::modelConvert);
    }

    private MenuDTO modelConvert(MenuDO menuEntity) {
        MenuDTO menu = Tools.Bean.copy(menuEntity, MenuDTO.class);
        if (null != menu.getMenuType()) {
            menu.setMenuTypeName(MenuTypeEnum.valueOf(menuEntity.getMenuType()).description);
        }
        if (null != menu.getMenuViewType()) {
            menu.setMenuViewTypeName(MenuViewTypeEnum.valueOf(menuEntity.getMenuViewType()).description);
        }
        if (null != menu.getJumpType()) {
            menu.setJumpTypeName(MenuJumpTypeEnum.valueOf(menuEntity.getJumpType()).description);
        }
        return menu;
    }

    private MenuDO createMenu(MenuDefinition menuDefinition,
                              String domainCode,
                              String tenantCode,
                              String appCode) {
        MenuDO entity = new MenuDO();
        entity.setId(Tools.Id.uuid());
        entity.setDomainCode(domainCode);
        entity.setTenantCode(tenantCode);
        entity.setAppCode(appCode);
        entity.setParentId(menuDefinition.parent());
        entity.setName(menuDefinition.description());
        entity.setEuName(menuDefinition.euName());
        entity.setPermissionCode(menuDefinition.permissionCode());
        entity.setMenuType(menuDefinition.type().name());
        entity.setJumpType(menuDefinition.jumpType().name());
        entity.setMenuViewType(MenuViewTypeEnum.MANAGE.name());
        entity.setComponent(menuDefinition.component());
        entity.setFramework(menuDefinition.framework());
        entity.setIcon(menuDefinition.icon());
        entity.setActiveIcon(menuDefinition.activeIcon());
        entity.setLogo(menuDefinition.logo());
        entity.setIframe(menuDefinition.iframe());
        entity.setDefaultOpened(false);
        entity.setPermanent(false);
        entity.setSidebar(menuDefinition.sidebar());
        entity.setBreadcrumb(menuDefinition.breadcrumb());
        entity.setActiveMenu(Tools.Str.EMPTY);
        entity.setCache(Tools.Str.EMPTY);
        entity.setNoCache(Tools.Str.EMPTY);
        entity.setBadge(Tools.Str.EMPTY);
        entity.setCopyright(false);
        entity.setPaddingBottom(Tools.Str.EMPTY);
        entity.setWhitelist(Tools.Str.EMPTY);
        entity.setLink(menuDefinition.link());
        entity.setIcon(menuDefinition.icon());
        entity.setFullPath(menuDefinition.fullPath());
        entity.setVisible(true);
        entity.setRemark(menuDefinition.description());
        entity.setSortOrder(menuDefinition.sortOrder());
        entity.setJumpType(menuDefinition.jumpType().name());
        entity.setKeepAlive(menuDefinition.keepAlive());
        entity.setPreset(true);
        return entity;
    }

    private void retrieveMenuParentIdAndFullPath(List<MenuDO> entities, Map<String, MenuDO> existMenuMap) {
        Map<String, List<MenuDO>> menuMap = Tools.Coll.convertGroup(entities, MenuDO::getDomainCode);
        menuMap.forEach((domainCode, domainMenuEntities) -> {
            Map<String, String> permissionCodeMap = Tools.Coll.convertMap(domainMenuEntities,
                    MenuDO::getPermissionCode,
                    entity -> existMenuMap.getOrDefault(entity.getParentId(), entity).getId());
            domainMenuEntities.forEach(domainMenuEntity -> {
                if (!permissionCodeMap.containsKey(domainMenuEntity.getParentId())) {
                    permissionCodeMap.put(domainMenuEntity.getParentId(), existMenuMap.containsKey(domainMenuEntity.getParentId()) ?
                            existMenuMap.get(domainMenuEntity.getParentId()).getId() : Tools.Str.EMPTY);
                }
            });
            for (MenuDO entity : domainMenuEntities) {
                entity.setParentId(permissionCodeMap.get(entity.getParentId()));
                entity.setFullPath(Tools.Coll.join(Tools.Coll.traverse(Tools.Str.split(entity.getFullPath(), "@"), permissionCodeMap::get), "@"));
            }
        });
    }

    private Map<String, Integer> calculateMenuCount(List<MenuDO> menuEntities) {
        if (Tools.Coll.isBlank(menuEntities)) {
            return new HashMap<>(0);
        }
        Map<String, Integer> menuCountMap = new HashMap<>(menuEntities.size());
        // 遍历每一个菜单节点
        for (MenuDO menuEntity : menuEntities) {
            // 获取当前节点的全路径，按@分割，结果为当前节点编号以及所有父节点编号
            String[] menuIds = menuEntity.getFullPath().split("@");
            // 遍历每一个节点编号
            for (String menuId : menuIds) {
                // 当前节点数量为1，当前节点所有父节点下的节点数量+1
                menuCountMap.put(menuId, menuCountMap.getOrDefault(menuId, 0) + 1);
            }
        }
        return menuCountMap;
    }

    public Integer getMaxSortOrder(String parentId, String appCode) {
        List<Integer> sortOrders = this.repo.listObjs(Wrappers.<MenuDO>lambdaQuery()
                .select(MenuDO::getSortOrder)
                .eq(MenuDO::getParentId, Tools.Str.isBlank(parentId) ? Tools.Str.EMPTY : parentId)
                .eq(Tools.Str.isNotBlank(Me.domainCode()), MenuDO::getDomainCode, Me.domainCode())
                .eq(Tools.Str.isNotBlank(Me.tenantCode()), MenuDO::getTenantCode, Me.tenantCode())
                .eq(Tools.Str.isNotBlank(appCode), MenuDO::getAppCode, appCode)
                .lt(MenuDO::getSortOrder, Integer.MAX_VALUE - 10000)
                .orderByDesc(MenuDO::getSortOrder));
        return Tools.Coll.isBlank(sortOrders) ? 0 : sortOrders.get(0);
    }
}
